/*
 * Copyright (C) Pietro Pilolli 2010 <pilolli.pietro@gmail.com>
 * 
 * Spotlighter is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Spotlighter is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include "callbacks.h"
#include "utils.h"


/* Main cairo context. */
static cairo_t *cairo_context = (cairo_t *) NULL;

/* Define the outbounded rectangle of the spotlight. */
static gdouble min_x = 0.0;
static gdouble max_x = 0.0;
static gdouble min_y = 0.0;
static gdouble max_y = 0.0;

/* The cursor to be displayed. */
static GdkCursor *cursor = (GdkCursor *) NULL;

/* See the discriminate_zone function... */
static gint zone = 5;

static gboolean first_press_event = TRUE;

static gint screen_width = 0;
static gint screen_height = 0;


/* Quit the program. */
static void
quit          ()
{
  if (cairo_context)
    {
      cairo_destroy (cairo_context);
    }
  if (cursor)
    {
      g_object_unref (cursor);
    }
  gtk_main_quit ();
  exit (1);
}


/* Draw the spotlighter hole. */
static void
draw          (cairo_t   *cairo_context,
               gdouble    x,
               gdouble    y,
               gdouble    width,
               gdouble    height,
               gchar     *color)
{
  if (cairo_context)
    {
      cairo_draw_ellipse (cairo_context,
                          min_x,
                          min_y,
                          max_x - min_x,
                          max_y - min_y,
                          color);
    }
}


/* Is the point (x,y) inside the rectangle? */
static gboolean
is_in_rectangle         (gdouble  x,
                         gdouble  y,
                         gdouble  min_x,
                         gdouble  min_y,
                         gdouble  max_x,
                         gdouble  max_y)
{
  if ((min_x < x) && (x < max_x) && (min_y < y) && (y < max_y))
    {
      return TRUE;
    }
  return FALSE;
}


/* Discriminate the zone. */
static void
discriminate_zone       (gdouble  x,
                         gdouble  y)
{
  gint tollerance = 30;
  GdkCursorType type = GDK_FLEUR;
  zone = 5;

  /*
     This is the topologic zone explained:

          1|1
       ---------
       |4|1|1|6|
       ---------
      0|0|5|5|2|2
      -----------
      0|0|5|5|2|2
       ---------
       |8|3|3|7|
       ---------
          3|3

  */

  if (is_in_rectangle (x, y, (min_x - tollerance), (min_y - tollerance), (max_x + tollerance), (max_y + tollerance)))
    {
      double delta_x = (max_x-min_x)/4;
      double delta_y = (max_y-min_y)/4;

      if (is_in_rectangle (x, y, (min_x + delta_x), (min_y - tollerance), (max_x - delta_x), (min_y + delta_y)))
        {
          zone = 1;
          type = GDK_TOP_SIDE;
        }
      else if (is_in_rectangle (x, y, (min_x + delta_x), (max_y - delta_y), (max_x - delta_x), (max_y + tollerance)))
        {
          zone = 3;
          type = GDK_BOTTOM_SIDE;
        }
      else if (is_in_rectangle (x, y, (min_x - tollerance), (min_y + delta_y), (min_x + delta_x), (max_y - delta_y)))
        {
          zone = 0;
          type = GDK_LEFT_SIDE;
        }
      else if (is_in_rectangle (x, y, (max_x - delta_x), (min_y + delta_y), (max_x + tollerance), (max_y - delta_y)))
        {
          zone = 2;
          type = GDK_RIGHT_SIDE;
        }
      else if (is_in_rectangle (x, y, (min_x + delta_x), (min_y + delta_y), (max_x - delta_x), (max_y - delta_y)))
        {
          zone = 5;
        }
      else if (is_in_rectangle (x, y, min_x, min_y, (min_x + delta_x), (min_y + delta_y)))
        {
          zone = 4;
          type = GDK_TOP_LEFT_CORNER;
        }
      else if (is_in_rectangle (x, y, (max_x - delta_x), min_y, max_x, (min_y + delta_y)))
        {
          zone = 6;
          type = GDK_TOP_RIGHT_CORNER;
        }
      else if (is_in_rectangle (x, y, (max_x - delta_x), (max_y - delta_y), max_x, max_y))
        {
          zone = 7;
          type = GDK_BOTTOM_RIGHT_CORNER;
        }
      else if (is_in_rectangle (x, y, min_x, (max_y - delta_y), (min_x + delta_x), max_y))
        {
          zone = 8;
          type = GDK_BOTTOM_LEFT_CORNER;
        }
    }
  if (cursor)
    {
      g_object_unref (cursor);
    }
  cursor = gdk_cursor_new (type);

}


/* Is the pointer above the close button? */
static gboolean
is_above_close_button        (gint  x,
                              gint  y)
{
  gint tollerance = 25;
  if ((x > (screen_width - tollerance)) &&
      (y < tollerance))
    {
      return TRUE;
    }
  return FALSE;
}


/* Destroy the program. */
G_MODULE_EXPORT gboolean 
destroy (GtkWidget *widget, gpointer data)
{
  quit ();
  return TRUE;
}


/* On screen changed. */
G_MODULE_EXPORT void
on_window_screen_changed       (GtkWidget  *widget,
                                GdkScreen  *previous_screen,
                                gpointer    user_data)
{
  GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (widget));
  GdkVisual *visual = gdk_screen_get_rgba_visual (screen);
  
  if (visual == NULL)
    {
      visual = gdk_screen_get_system_visual (screen);
    }

  gtk_widget_set_visual (widget, visual);
}

/* On configure event. */
G_MODULE_EXPORT gboolean
on_window_configure       (GtkWidget       *widget,
                           GdkEventExpose  *event,
                           gpointer        user_data)
{
    GdkWindowState state = gdk_window_get_state (gtk_widget_get_window (widget));
  gint is_fullscreen = state & GDK_WINDOW_STATE_FULLSCREEN;
  if (is_fullscreen <= 0)
    {
      return FALSE;
    }
  if (!cairo_context)
    {
      screen_width = gdk_screen_width ();
      screen_height = gdk_screen_height ();
      cairo_context = gdk_cairo_create (gtk_widget_get_window (widget));
      gtk_window_set_opacity (GTK_WINDOW (widget), 1);
      
      gtk_widget_input_shape_combine_region(widget, NULL);
    }
  return TRUE;
}


/* Exposure event. */
G_MODULE_EXPORT gboolean 
on_window_expose_event       (GtkWidget  *widget,
                              cairo_t    *cr,
                              gpointer    user_data)
{
  if (cairo_context)
    {
      /* Initialize the cairo context pen options. */
      cairo_set_operator (cairo_context, CAIRO_OPERATOR_SOURCE);
      cairo_set_line_cap (cairo_context, CAIRO_LINE_CAP_ROUND);
      cairo_set_line_join (cairo_context, CAIRO_LINE_JOIN_ROUND);
      color_window (cairo_context, BACKGROUND_COLOR, gdk_screen_width ());
    }

  gtk_widget_input_shape_combine_region (widget, NULL);
  return TRUE;
}


/* Called when a press event occurs. */
G_MODULE_EXPORT gboolean 
on_window_button_press_event      (GtkWindow       *window,
                                   GdkEventButton  *event,
                                   gpointer         data)
{
  if (is_above_close_button (event->x, event->y))
    {
      quit ();
    }
  if (first_press_event)
    {
      gdouble threshold = 150;
      max_x = event->x + threshold;
      max_y = event->y + threshold;
      min_x = event->x - threshold;
      min_y = event->y - threshold;
      first_press_event = FALSE;
     }

  gtk_widget_input_shape_combine_region (GTK_WIDGET (window), NULL);

  return TRUE;
}


/* Called when a motion event occurs. */
G_MODULE_EXPORT gboolean 
on_window_motion_event       (GtkWidget       *widget,
                              GdkEventButton  *event,
                              gpointer         data)
{
  GdkModifierType state = (GdkModifierType) event->state;
  /* Only if button1 is not pushed. */
  if (!(state & GDK_BUTTON1_MASK))
    {
      if (is_above_close_button (event->x, event->y))
        {
          gdk_window_set_cursor (gtk_widget_get_window (widget), NULL);
        }
      else
        {
          discriminate_zone (event->x,event->y);  
          gdk_window_set_cursor (gtk_widget_get_window (widget), cursor);
        }
    }
  return TRUE;
}


/* Called when a release event occurs. */
G_MODULE_EXPORT gboolean 
on_window_button_release_event    (GtkWidget       *widget,
                                   GdkEventButton  *event,
                                   gpointer         data)
{
  /* Only button1 allowed. */
  if (event->button != 1)
    {
      return FALSE;
    }

  gint spotlighter_minimum_size = 60;
  gdk_window_set_cursor (gtk_widget_get_window (widget), NULL);

  if (cairo_context)
    {
      color_window (cairo_context, BACKGROUND_COLOR, screen_width);
    }

  if (zone == 5)
    {
      gdouble centerdelta_x = event->x - (min_x + (max_x - min_x)/2);
      gdouble centerdelta_y = event->y - (min_y + (max_y - min_y)/2);
      min_x = min_x + centerdelta_x;
      min_y = min_y + centerdelta_y;
      max_x = max_x + centerdelta_x;
      max_y = max_y + centerdelta_y;
    }
  else if (zone == 1)
    {
      if (fabs (min_y - event->y) > spotlighter_minimum_size)
        {
          min_y = event->y;
        }
    }
  else if (zone == 3)
    {
      if (fabs (min_y - event->y) > spotlighter_minimum_size)
        {
          max_y = event->y;
        }
    }
  else if (zone == 0)
    {
      if (fabs (min_x - event->x) > spotlighter_minimum_size)
        {
          min_x = event->x;
        }
    }
  else if (zone == 2)
    {
      if (fabs (min_x - event->x) > spotlighter_minimum_size)
        {
          max_x = event->x;
        }
    }
  else if (zone == 4)
    {
      if ((fabs (max_y - event->y) > spotlighter_minimum_size) &&
          (fabs (max_x - event->x) > spotlighter_minimum_size))
        {
          min_x = event->x;
          min_y = event->y;
        }
    }
  else if (zone == 6)
    {
      if ((fabs (max_y - event->y) > spotlighter_minimum_size) &&
          (fabs (min_x - event->x) > spotlighter_minimum_size))
        {
          min_y = event->y;
          max_x = event->x;
        }
    }
  else if (zone == 7)
    {
      if ((fabs (min_y-event->y) > spotlighter_minimum_size) &&
          (fabs (min_x-event->x) > spotlighter_minimum_size))
        {
          max_x = event->x;
          max_y = event->y;
        }
    }
  else if (zone == 8)
    {
      if ((fabs (min_y-event->y) > spotlighter_minimum_size) &&
          (fabs (max_x-event->x) > spotlighter_minimum_size))
        {
          max_y = event->y;
          min_x = event->x;
        }
    }

  draw (cairo_context,
        min_x,
        min_y,
        (max_x - min_x),
        (max_y - min_y),
        SPOTLIGHTER_COLOR);

  const cairo_rectangle_int_t all_rect = {0,
                                          0,
                                          gdk_screen_width (),
                                          gdk_screen_height ()};
  
  cairo_region_t* r = gdk_cairo_region_create_from_surface (cairo_get_target (cairo_context) );
  gtk_widget_input_shape_combine_region (widget, r);
  cairo_region_destroy (r);
  
  return TRUE;
}


/* Called when a destroy event occurs. */
G_MODULE_EXPORT gboolean 
on_window_destroy_event      (GtkWindow  *self,
                              GdkEvent   *event,
                              gpointer    user_data)
{
  quit ();
  return TRUE;
}


